<style>
	html,body{ margin: 0; padding: 0; }
	#canvas{ display: block; width: 100%; height:100%; margin: 5px auto 0; }
</style>

<canvas id="canvas"></canvas>

<script>

	class Gobang{
		constructor(options){
			this.initElement(options);
			this.initScale(options);
			this.initStyle(options);
			this.initPlayer(options);
			this.viewChoose();
		}

		// 初始化绘制元素
		initElement({element}){
			if(!element){ throw new Error("初始化元素不能为空"); }
			this.canvas = element;
			this._2d = this.canvas.getContext("2d");
		}

		// 初始化规格
		initScale({width,height,widthLength,heightLength}){
			this.width = width;
			this.height = height;
			this.widthLength = widthLength;
			this.heightLength = heightLength;
			this.widthEvery = this.width/this.widthLength;
			this.heightEvery = this.height/this.heightLength;
			this.xLength = this.widthLength-1;
			this.yLength = this.heightLength-1;
			this.initDrawSize();
		}

		// 初始化绘制大小
		initDrawSize(){
			const width = this.width;
			const height = this.height;
			const canvas = this.canvas;
			canvas.width = width;
			canvas.height = height;
			canvas.style.width = width;
			canvas.style.height = height;
		}

		//初始化样式
		initStyle({lineColor,backgroundColor}){
			this.lineColor = lineColor;
			this.backgroundColor = backgroundColor;
		}

		// 初始化播放列表
		initPlayer({playerList}){
			this.playerList = playerList;
		}

		// 添加事件
		addEvent(type,fn,useCapture){
			const canvas = this.canvas;
			canvas.addEventListener(type,fn,useCapture);
			this.EventList.push(()=>{
				canvas.removeEventListener(type,fn,useCapture);
			});
		}

		// 移除事件
		removeEvent(){
			if(this.EventList){
				this.EventList.forEach(removeEvent=>{
					removeEvent();
				});
			}
			this.EventList = [];
		}

		// 重置游戏棋盘
		resetPlayBoard(){
			const xLength = this.xLength;
			const yLength = this.yLength;
			this.playerIndex = 0;
			this.pieceHover = null;
			this.chessBoard = new Array(xLength).fill(null).map(_=>
				new Array(yLength).fill(null)
			);
		}

		// 添加游戏玩家
		appendPlayer(player){
			this.playerList.push(player);
		}

		// 绘制游戏背景
		drawPlayBackground = function(){
			const _2d = this._2d;
			const width = this.width;
			const height = this.height;
			const lineColor = this.lineColor;
			const backgroundColor = this.backgroundColor;
			_2d.save();
			_2d.fillStyle = backgroundColor;
			_2d.fillRect(0, 0, width, height);
			_2d.strokeStyle = lineColor;
			_2d.strokeRect(0, 0, width, height);
			_2d.restore();
		}

		// 绘制游戏X轴
		drawPlayXLine(){
			const _2d = this._2d;
			const width = this.width;
			const length = this.heightLength;
			const every = this.widthEvery;
			_2d.save();
			_2d.strokeStyle = this.lineColor;
			for(let i=0; i<length+1; i++){
				const y = every*i;
				_2d.beginPath();
				_2d.moveTo(0,y);
				_2d.lineTo(width,y);
				_2d.stroke();
			}
			_2d.restore();
		}

		// 绘制游戏Y轴
		drawPlayYLine(){
			const _2d = this._2d;
			const height = this.height;
			const length = this.widthLength;
			const every = this.heightEvery;
			_2d.save();
			_2d.strokeStyle = this.lineColor;
			for(let i=0; i<length+1; i++){
				const x = every*i;
				_2d.beginPath();
				_2d.moveTo(x,0);
				_2d.lineTo(x,height);
				_2d.stroke();
			}
			_2d.restore();
		}

		// 绘制游戏棋子
		drawPlayPiece(){
			const _2d = this._2d;
			const widthEvery = this.widthEvery;
			const heightEvery = this.heightEvery;
			const min = Math.min(widthEvery,heightEvery);
			const lineColor = this.lineColor;
			const chessBoard = this.chessBoard;
			const playerList = this.playerList;
			const playerLength = this.playerList.length;
			_2d.save();
			chessBoard.forEach((xData,xIndex)=>xData.forEach((playerIndex,yIndex)=>{
				if(playerIndex!=null && playerIndex<playerLength){
					const x = (xIndex+1)*widthEvery;
					const y = (yIndex+1)*heightEvery;
					const playInfo = playerList[playerIndex];
					_2d.strokeStyle = lineColor;
					_2d.fillStyle = playInfo.color;
					_2d.beginPath();
					_2d.arc(x,y,min/2*.8,0,Math.PI*2);
					_2d.fill();
					_2d.stroke();
				}
			}));
			const pieceHover = this.pieceHover;
			if(pieceHover){
				const playerIndex = this.playerIndex;
				const [xIndex,yIndex] = pieceHover;
				const value = chessBoard[xIndex][yIndex];
				if(value == null){
					const x = (xIndex+1)*widthEvery;
					const y = (yIndex+1)*heightEvery;
					const playInfo = playerList[playerIndex];
					_2d.globalAlpha = 0.3;
					_2d.strokeStyle = lineColor;
					_2d.fillStyle = playInfo.color;
					_2d.beginPath();
					_2d.arc(x,y,min/2*.8,0,Math.PI*2);
					_2d.fill();
					_2d.stroke();
					_2d.globalAlpha = 1;
				}
			}
			_2d.restore();
		}

		// 绘制游戏棋局
		drawPlay(){
			this.drawPlayBackground();
			this.drawPlayXLine();
			this.drawPlayYLine();
			this.drawPlayPiece();
		}

		// 检查一个棋盘棋子
		checkPlayOne(xIndex,yIndex,playerIndex){
			const col = this.chessBoard[xIndex];
			return col && col[yIndex] == playerIndex;
		}

		// 检查X轴棋盘棋子
		checkPlayX(xIndex,yIndex,playerIndex){
			let count = 1;
			for(let i=1; i<5; i++){
				if(this.checkPlayOne(xIndex-i,yIndex,playerIndex)){
					count++;
				}else{
					break;
				}
			}
			for(let i=1; i<5; i++){
				if(this.checkPlayOne(xIndex+i,yIndex,playerIndex)){
					count++;
				}else{
					break;
				}
			}
			return count>=5;
		}

		// 检查Y轴棋盘棋子
		checkPlayY(xIndex,yIndex,playerIndex){
			let count = 1;
			for(let i=1; i<5; i++){
				if(this.checkPlayOne(xIndex,yIndex-i,playerIndex)){
					count++;
				}else{
					break;
				}
			}
			for(let i=1; i<5; i++){
				if(this.checkPlayOne(xIndex,yIndex+i,playerIndex)){
					count++;
				}else{
					break;
				}
			}
			return count>=5;
		}

		// 检查左上到右下棋盘棋子
		checkPlayLeftTop(xIndex,yIndex,playerIndex){
			let count = 1;
			for(let i=1; i<5; i++){
				if(this.checkPlayOne(xIndex-i,yIndex-i,playerIndex)){
					count++;
				}else{
					break;
				}
			}
			for(let i=1; i<5; i++){
				if(this.checkPlayOne(xIndex+i,yIndex+i,playerIndex)){
					count++;
				}else{
					break;
				}
			}
			return count>=5;
		}

		// 检查左下到右上棋盘棋子
		checkLeftButton(xIndex,yIndex,playerIndex){
			let count = 1;
			for(let i=1; i<5; i++){
				if(this.checkPlayOne(xIndex-i,yIndex+i,playerIndex)){
					count++;
				}else{
					break;
				}
			}
			for(let i=1; i<5; i++){
				if(this.checkPlayOne(xIndex+i,yIndex-i,playerIndex)){
					count++;
				}else{
					break;
				}
			}
			return count>=5;
		}

		// 检查游戏是否结束
		checkPlayGameOver(xIndex,yIndex,playerIndex){
			return this.checkPlayX(xIndex,yIndex,playerIndex)||
				this.checkPlayY(xIndex,yIndex,playerIndex)||
				this.checkPlayLeftTop(xIndex,yIndex,playerIndex)||
				this.checkLeftButton(xIndex,yIndex,playerIndex);
		}

		// 落下棋子点
		nextPlayChess(position){
			if(position == null){ return; }
			const [xIndex,yIndex] = position;
			const xLength = this.xLength;
			const yLength = this.yLength;
			const chessBoard = this.chessBoard;
			const playerIndex = this.playerIndex;
			const playerLength = this.playerList.length;
			if(xIndex>xLength){ throw new Error("下棋横向位置超出范围"); }
			if(yIndex>yLength){ throw new Error("下棋横向位置超出范围"); }
			const value = chessBoard[xIndex][yIndex];
			if(value != null){ return; }
			chessBoard[xIndex][yIndex] = playerIndex;
			const isGameOver = this.checkPlayGameOver(xIndex,yIndex,playerIndex);
			if(isGameOver){
				this.playGameOver();
				return;
			}else{
				if(++this.playerIndex>=playerLength){
					this.playerIndex=0;
				}
			}
			this.drawPlay();
		}

		// 落下棋子坐标
		nexPlayPointtChess(x,y){
			this.nextPlayChess(this.getPlayPieceIndex(x,y));
		}

		// 鼠标悬停点
		setPlayPieceHover(position){
			this.pieceHover = position;
			this.drawPlay();
		}

		// 鼠标悬停坐标
		setPointHover(x,y){
			this.setPlayPieceHover(this.getPlayPieceIndex(x,y));
		}

		// 取坐标的点位置
		getPlayPieceIndex(left,top){
			const xLength = this.xLength;
			const yLength = this.yLength;
			const widthEvery = this.widthEvery;
			const heightEvery = this.heightEvery;
			const widthEveryHalf = widthEvery/2;
			const heightEveryHalf = heightEvery/2;
			const x=left-widthEveryHalf;
			const y=top-heightEveryHalf;
			if(x<0 || y<0){ return null; }
			const xIndex = Math.ceil(x/widthEvery)-1;
			const yIndex = Math.ceil(y/heightEvery)-1;
			if(xIndex>xLength-1 || yIndex>yLength-1){ return null; }
			return [xIndex, yIndex];
		};

		// 添加游戏事件
		addPlayEvent(){
			this.removeEvent();
			this.addEvent("click",e=>{
				this.nexPlayPointtChess(e.offsetX,e.offsetY);
			});

			this.addEvent("mousemove",e=>{
				this.setPointHover(e.offsetX,e.offsetY);
			});

			this.addEvent("mouseleave",e=>{
				this.setPlayPieceHover(null);
			});
		}

		// 游戏结束
		playGameOver(){
			const playInfo = this.playerList[this.playerIndex];
			alert(`${playInfo.name}获得游戏的胜利`);
			this.resetPlayBoard();
		}

		// 进入游戏界面
		viewPlay(){
			this.resetPlayBoard();
			this.addPlayEvent();
			this.drawPlay();
		}

		// 初始化选择界面元素
		resetChooseElement(){
			const width = this.width;
			const height = this.height;
			const widthSize = 85;
			const heightSize = 20;
			const context = "开始游戏";
			this.chooseButton={
				left:(width-widthSize)/2,
				top:(height-heightSize)/2,
				width: widthSize,
				height: heightSize,
			};
			this.chooseContext={
				string:context,
				size:heightSize*.8,
				left:width/2,
				top:height/2+1,
			};
		}

		// 绘制游戏棋局
		drawChoose(){
			const _2d = this._2d;
			const width = this.width;
			const height = this.height;
			const lineColor = this.lineColor;
			const backgroundColor = this.backgroundColor;
			const btn = this.chooseButton;
			const text = this.chooseContext;
			_2d.save();
			_2d.fillStyle = backgroundColor;
			_2d.fillRect(0, 0, width, height);
			_2d.strokeStyle = lineColor;
			_2d.strokeRect(0, 0, width, height);
			_2d.strokeRect(btn.left, btn.top, btn.width, btn.height);
			_2d.font = `${text.size}px 微软雅黑`;
			_2d.textAlign = "center";
			_2d.textBaseline = "middle";
			_2d.fillStyle = lineColor;
			_2d.fillText(text.string, text.left, text.top);
			_2d.restore();
		}

		
		// 添加游戏事件
		addChooseEvent(){
			const btn = this.chooseButton;
			this.removeEvent();
			this.addEvent("click",e=>{
				const x = e.offsetX;
				const y = e.offsetY;
				const {left,top,width,height} = btn;
				if(x>=left && x<=left+width && y>=top && y<=top+height){
					this.viewPlay();
				}
			});

			this.addEvent("mousemove",e=>{
				this.drawChoose();
			});

			this.addEvent("mouseleave",e=>{
				this.drawChoose();
			});
		}

		// 进入选择界面
		viewChoose(){
			this.resetChooseElement();
			this.addChooseEvent();
			this.drawChoose();
		}
	}

	new Gobang({
		element: document.querySelector("#canvas"),
		width: 600,
		height: 600,
		widthLength: 20,
		heightLength: 20,
		lineColor: "#010",
		backgroundColor: "#999",
		playerList: [
			{ name: "玩家1", color: "#0f0" },
			{ name: "玩家2", color: "#f00" },
		],
	});
</script>